/**
 * \file
 *         Trickle (reliable single source flooding) for Ipv6
 */

#include "net/tcpip.h"
#include "net/uip.h"
#include "net/uip-ds6.h"
#include "net/uip-nd6.h"
#include "net/uip-icmp6.h"
#include "collect-common.h"

#include "trickle-ipv6.h"
#include "lib/random.h"

#define INTERVAL_MIN 1
#define INTERVAL_MAX 10

#define DUPLICATE_THRESHOLD 1

#define SEQNO_LT(a, b) ((signed char)((a) - (b)) < 0)

#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#define PRINT6ADDR(addr) PRINTF(" %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ", ((u8_t *)addr)[0], ((u8_t *)addr)[1], ((u8_t *)addr)[2], ((u8_t *)addr)[3], ((u8_t *)addr)[4], ((u8_t *)addr)[5], ((u8_t *)addr)[6], ((u8_t *)addr)[7], ((u8_t *)addr)[8], ((u8_t *)addr)[9], ((u8_t *)addr)[10], ((u8_t *)addr)[11], ((u8_t *)addr)[12], ((u8_t *)addr)[13], ((u8_t *)addr)[14], ((u8_t *)addr)[15])
#else
#define PRINTF(...)
#define PRINT6ADDR(addr)
#endif

#define UIP_IP_BUF       ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])
#define UIP_ICMP_BUF     ((struct uip_icmp_hdr *)&uip_buf[uip_l2_l3_hdr_len])
#define UIP_ICMP_PAYLOAD ((unsigned char *)&uip_buf[uip_l2_l3_icmp_hdr_len])


static struct trickle_ipv6 trickle_ipv6_conn;
struct trickle_ipv6 *c = &trickle_ipv6_conn;
static int run_trickle(struct trickle_ipv6 *c);
/*---------------------------------------------------------------------------*/
static void
trickle_output(void *ptr)
{
  struct trickle_ipv6 *c = ptr;
  unsigned char *buffer;
  int pos;
  uip_ipaddr_t addr;
  uint32_t time_seconds_now;

  pos = 0;

  buffer = UIP_ICMP_PAYLOAD;
  buffer[pos++] = c->seqno;
  buffer[pos++] = samplePeriod >> 8;
  buffer[pos++] = samplePeriod & 0xff;
  time_seconds_now = get_time();
  buffer[pos++] = time_seconds_now >> 24;
  buffer[pos++] = (time_seconds_now >> 16) & 0xff;
  buffer[pos++] = (time_seconds_now >> 8) & 0xff;
  buffer[pos++] = time_seconds_now & 0xff;
  buffer[pos++] = collect_activated;

  PRINTF("TRICKLE: trickle msg sent\n");
  uip_create_linklocal_tricklenodes_mcast(&addr);
  uip_icmp6_send(&addr, ICMP6_TRICKLE, TRICKLE_MSG, pos);

}

/*---------------------------------------------------------------------------*/
static void
timer_callback(void *ptr)
{
  struct trickle_ipv6 *c = ptr;
  run_trickle(c);
}
/*---------------------------------------------------------------------------*/
static void
reset_interval(struct trickle_ipv6 *c)
{
  PT_INIT(&c->pt);
  run_trickle(c);
}
/*---------------------------------------------------------------------------*/
static void
set_timer(struct trickle_ipv6 *c, struct ctimer *t, clock_time_t i)
{
  ctimer_set(t, i, timer_callback, c);
}
/*---------------------------------------------------------------------------*/
static int
run_trickle(struct trickle_ipv6 *c)
{
  clock_time_t interval;
  PT_BEGIN(&c->pt);

  while(1) {
    interval = c->interval << c->interval_scaling;
    set_timer(c, &c->interval_timer, interval);
    set_timer(c, &c->t, interval / 2 + (random_rand() % (interval / 2)));

    c->duplicates = 0;
    PT_YIELD(&c->pt); /* Wait until listen timeout */
    if(c->duplicates < DUPLICATE_THRESHOLD) {
      trickle_output(c);
    } else  {
      //REVISAR: Hacer que el sink nunca evite retransmisiones
      // De esta manera siempre los que la evitan son los vecinos
      // Ojo nodo aislado transmite como loco hacer intervalos muy largos
      PRINTF("TRICKLE: retransmission avoided\n");
    }
    PT_YIELD(&c->pt); /* Wait until interval timer expired. */
    PRINTF("TRICKLE: interval scaling %u\n", c->interval_scaling);
    if(c->interval_scaling < INTERVAL_MAX) {
      c->interval_scaling++;
    }
  }

  PT_END(&c->pt);
}
/*---------------------------------------------------------------------------*/
void
trickle_ipv6_input(void)
{
  uint32_t time_seconds_received;
  uip_ipaddr_t trickle_sender_addr;
  unsigned char *buffer;
  uint16_t seqno;
  uint8_t buffer_length;
  int pos;

  uip_ipaddr_copy(&trickle_sender_addr, &UIP_IP_BUF->srcipaddr);

  PRINTF("TRICKLE: Received a Trickle msg from ");
  PRINT6ADDR(&trickle_sender_addr);
  PRINTF("\n");

  buffer = UIP_ICMP_PAYLOAD;
  buffer_length = uip_len - uip_l2_l3_icmp_hdr_len;

  pos = 0;
  seqno = buffer[pos++];


  PRINTF("TRICKLE: recv seqno %d , our seqno is %d \n",
       seqno,
       c->seqno);


  if(seqno == c->seqno) {
    ++c->duplicates;
    PRINTF("TRICKLE: msg duplicate  number %d\n",
           c->duplicates);
  } else if(SEQNO_LT(seqno, c->seqno)) {
    PRINTF("TRICKLE: msg received is older %d\n",
           c->duplicates);
    c->interval_scaling = 0;
    trickle_output(c);
  } else {
    PRINTF("TRICKLE: msg received is newer, updating data...\n");
    c->seqno = seqno;

    samplePeriod = (buffer[pos] << 8) | buffer[pos + 1];
    pos += 2;

    etimer_change_only_interval(&store_timer, CLOCK_SECOND * samplePeriod);

    PRINTF("TRICKLE: New samplePeriod received = %u\n", samplePeriod);

    transmissionPeriod = samplePeriod >> 1;

    etimer_change_only_interval(&send_timer_ipv6, CLOCK_SECOND * transmissionPeriod);

    PRINTF("TRICKLE: New transmissionPeriod set = %u\n", transmissionPeriod);

    randWaitPeriod = transmissionPeriod;

    PRINTF("TRICKLE: New randWaitPeriod set = %u\n", randWaitPeriod);

    time_seconds_received = ((uint32_t)buffer[pos] << 24) | ((uint32_t)buffer[pos+1] << 16) | ((uint32_t)buffer[pos+2] << 8) | buffer[pos + 3];
    pos += 4;

    set_time(time_seconds_received);

    PRINTF("TRICKLE: New time_seconds_received= %lu\n", time_seconds_received);

    collect_activated = buffer[pos];
    pos += 1;

    PRINTF("TRICKLE: collect_activated= %u\n", collect_activated);

    //REVISAR: Procesar configuracion recibida (cuando se incluya)
    c->interval_scaling = 0;
    reset_interval(c);
    ctimer_set(&c->first_transmission_timer, random_rand() % c->interval,
	       trickle_output, c);
  }
}

/*---------------------------------------------------------------------------*/
void
trickle_ipv6_init(clock_time_t interval)
{
  uip_ipaddr_t tricklemaddr;

  c->interval = interval;
  c->interval_scaling = 0;

  /* add trickle multicast address */
  uip_create_linklocal_tricklenodes_mcast(&tricklemaddr);
  uip_ds6_maddr_add(&tricklemaddr);
}

/*---------------------------------------------------------------------------*/
void
trickle_ipv6_send(void)
{
  c->seqno++;

  PRINTF("TRICKLE: send seqno %d\n", c->seqno);
  reset_interval(c);
  trickle_output(c);
}
/*---------------------------------------------------------------------------*/
/** @} */
